using MediatR;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using NotifyCenter.EntityFrameworkCore;
using NotifyCenter.MediatR.Etos;
using NotifyCenter.NotificationRecords;
using NotifyCenter.Projects;

namespace NotifyCenter.MediatR.Handlers;

public class MessageEnqueueHandler(
    IMediator publisher,
    NotifyCenterDbContext dbContext,
    IEnumerable<ISender> senders,
    ILogger<MessageEnqueueHandler> logger) : INotificationHandler<MessageEnqueueEto>
{
    private const int DefaultMaxRetryTimes = 3;

    public async Task Handle(MessageEnqueueEto eto, CancellationToken cancellationToken)
    {
        var record = await dbContext.Set<NotificationRecord>()
            .FirstOrDefaultAsync(r => r.Id == eto.RecordId, cancellationToken);

        if (record is null)
        {
            logger.LogWarning("Record {RecordId} does not exist. Skip sending.", eto.RecordId);
            return;
        }

        var sender = senders.FirstOrDefault(t => t.Type == eto.Type);
        if (sender is null)
        {
            await MarkFailureAsync(record, $"Sender for type {eto.Type} is not registered.", cancellationToken);
            await PublishStatusAsync(record.Id, MessageSendingStatus.Failed, $"Sending failed: sender for {eto.Type} is not registered.", cancellationToken);
            return;
        }

        record.Status = MessageSendingStatus.Sending;
        record.RetryTime = eto.Attempt;
        record.ErrorMsg = string.Empty;
        await dbContext.SaveChangesAsync(cancellationToken);

        try
        {
            await sender.SendAsync(eto.ConfigurationJson, eto.Receiver, eto.Content, cancellationToken);

            record.Status = MessageSendingStatus.Success;
            record.SentAt = DateTime.UtcNow;
            record.ErrorMsg = string.Empty;
            await dbContext.SaveChangesAsync(cancellationToken);

            await PublishStatusAsync(record.Id, MessageSendingStatus.Success, "Sent successfully.", cancellationToken);
        }
        catch (Exception ex)
        {
            await HandleFailureAsync(record, eto, ex, cancellationToken);
        }
    }

    private async Task HandleFailureAsync(NotificationRecord record, MessageEnqueueEto eto, Exception ex, CancellationToken cancellationToken)
    {
        record.Status = MessageSendingStatus.Failed;
        record.SentAt = DateTime.UtcNow;
        record.ErrorMsg = ex.Message;
        await dbContext.SaveChangesAsync(cancellationToken);

        var maxRetryTimes = await dbContext.Set<NotificationRule>()
            .Where(rule => rule.Id == record.NotificationRuleId)
            .Select(rule => rule.MaxRetryTimes)
            .FirstOrDefaultAsync(cancellationToken);

        var allowedAttempts = maxRetryTimes > 0 ? maxRetryTimes : DefaultMaxRetryTimes;

        if (eto.Attempt < allowedAttempts)
        {
            record.Status = MessageSendingStatus.Pending;
            await dbContext.SaveChangesAsync(cancellationToken);

            logger.LogWarning(ex, "Send failed, scheduling attempt {Attempt}/{Allowed} for record {RecordId}", eto.Attempt + 1, allowedAttempts, record.Id);

            await publisher.Publish(new MessageEnqueueEto
            {
                RecordId = eto.RecordId,
                Type = eto.Type,
                ConfigurationJson = eto.ConfigurationJson,
                Receiver = eto.Receiver,
                Content = eto.Content,
                Attempt = eto.Attempt + 1
            }, cancellationToken);

            return;
        }

        logger.LogError(ex, "Send failed and max retry attempts reached for record {RecordId}", record.Id);
        await PublishStatusAsync(record.Id, MessageSendingStatus.Failed, $"Sending failed: {ex.Message}", cancellationToken);
    }

    private async Task MarkFailureAsync(NotificationRecord record, string message, CancellationToken cancellationToken)
    {
        record.Status = MessageSendingStatus.Failed;
        record.SentAt = DateTime.UtcNow;
        record.ErrorMsg = message;
        await dbContext.SaveChangesAsync(cancellationToken);
    }

    private Task PublishStatusAsync(long recordId, MessageSendingStatus status, string message, CancellationToken cancellationToken)
    {
        return publisher.Publish(new MessageSendingStatusChangedEto
        {
            RecordId = recordId,
            Type = status,
            Message = message
        }, cancellationToken);
    }
}
